package dm.bsi;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Iterator;

import org.dom4j.DocumentException;
import org.junit.experimental.theories.Theories;

import indi.zjh.dp.db.Session;
import indi.zjh.dp.error.CommandExecuteException;
import indi.zjh.dp.error.UnsuportedFieldException;
import indi.zjh.dp.orm.Query;
import indi.zjh.dp.orm.Relation;
import indi.zjh.dp.orm.Result;
import indi.zjh.dp.startup.Constant;
import indi.zjh.dp.util.FileUtil;
import indi.zjh.dp.util.SystemCmd;
import indi.zjh.dp.util.XMLUtil;

public class Service extends LogNode {
	public static final int PROTO_HTTP = 0;
	public static final int PROTO_TCP = 1;
	
	public static final int CATALOG_NORMAL = 0; // dp服务
	public static final int CATALOG_REDIS_SENTINEL = 1; // redis sentinel集群
	public static final int CATALOG_REDIS_CLUSTER = 2; // redis cluster集群
	public static final int CATELOG_MYSQL = 3;
	
	private static String servicePortStr = null;
	private static int servicePortStart = 0;
	private static int servicePortEnd = 0;
	
	protected int port = 0;
	protected int secondPort = 0;
	protected String name;
	protected int proto = PROTO_HTTP;
	protected int catalog = CATALOG_NORMAL;
	protected boolean isBackup = false;
	
	protected Relation containers = new Relation(Container.class);
	protected Relation deployment = new Relation(Deployment.class);
	protected Relation backups = new Relation(Backup.class);
	/**
	 * 获取关联的Node对象
	 * @param session
	 * @return
	 * @throws UnsuportedFieldException
	 * @throws DocumentException
	 * @throws Exception
	 */
	public Deployment getDeployment(Session session) throws UnsuportedFieldException, DocumentException, Exception
	{
		if(this.deployment.size() == 0)
			return null;
		long deployid = this.deployment.getIds()[0];
		Deployment deploy = (Deployment)session.getById(Deployment.class, deployid);
		return deploy;
	}
	
	/**
	 * 获取该服务关联的正在运行的容器数组
	 * @param session
	 * @return
	 * @throws Exception 
	 * @throws DocumentException 
	 * @throws UnsuportedFieldException 
	 */
	public Container[] getRunningContainers(Session session) throws UnsuportedFieldException, DocumentException, Exception
	{
		long[] ids = this.containers.getIds();
		ArrayList<Container> containers = new ArrayList<Container>();
		for(int i = 0; i < ids.length; ++i)
		{
			Container container = (Container)session.getById(Container.class, ids[i]);
			if(container.getState() == Container.RUNNING)
			{
				containers.add(container);
			}
		}
		Container[] declare = new Container[containers.size()];
		return containers.toArray(declare);
	}
	
	/**
	 * 获取所有的服务对象列表
	 * @param session
	 * @return
	 * @throws SQLException
	 * @throws DocumentException
	 * @throws UnsuportedFieldException
	 * @throws Exception
	 */
	public static Service[] listServices(Session session) throws SQLException, DocumentException, UnsuportedFieldException, Exception
	{
		Query<Service> query = new Query<Service>(session);
		Result<Service> result = query.find(Service.class).result();
		Iterator<Service> iter = result.iterator();
		Service[] services = new Service[result.count()];
		int i = 0;
		while(iter.hasNext())
		{
			services[i++] = iter.next();
		}
		return services;
	}
	
	/**
	 * 当容器发生添加和删除时要更新服务
	 * @param session
	 * @throws SQLException
	 * @throws DocumentException
	 * @throws UnsuportedFieldException
	 * @throws Exception
	 */
	public static void agentUpdate(Session session) throws SQLException, DocumentException, UnsuportedFieldException, Exception
	{
		// 获取所有的服务
		Service[] services = listServices(session);
		String template = null;//FileUtil.getFileContent("conf/nginx.template");
		StringBuffer sb = new StringBuffer();
		
		
		
		// 更新dns映射
		StringBuffer forwardBuf = new StringBuffer();
		StringBuffer reverseBuf = new StringBuffer();
		for(int i = 0; i < services.length; ++i)
		{
			long[] containerIds = services[i].getContainers().getIds();
			boolean isFirstContainer = true;
			if(containerIds.length > 0)
			{	
				for(int j = 0; j < containerIds.length; ++j)
				{
					Container curContainer = (Container)session.getById(Container.class, containerIds[j]);
					if(curContainer.getRole() == Container.ROLE_WORKER || curContainer.getRole() == Container.ROLE_MASTER)
					{
						Node curNode = curContainer.getNode(session);
						String[] ipparts = curNode.getIp().split("\\.");
						
						if(isFirstContainer)
						{
							forwardBuf.append("hb\tIN\tA\t"+curNode.getIp()+"\n");
							reverseBuf.append(ipparts[3]+"\tIN\tPTR\thb.juooo.service.\n");
							isFirstContainer = false;
						}
						
						forwardBuf.append(services[i].getName()+"\tIN\tA\t"+curNode.getIp()+"\n");
						reverseBuf.append(ipparts[3]+"\tIN\tPTR\t"+services[i].getName()+".juooo.service.\n");
					}
					
					// mysql 读写分离
					if(services[i].getCatalog() == Service.CATELOG_MYSQL)
					{
						if(curContainer.getRole() == Container.ROLE_SLAVE)
						{
							Node curNode = curContainer.getNode(session);
							String[] ipparts = curNode.getIp().split("\\.");
							
							forwardBuf.append(services[i].getName()+"r\tIN\tA\t"+curNode.getIp()+"\n");
							reverseBuf.append(ipparts[3]+"\tIN\tPTR\t"+services[i].getName()+"r.juooo.service.\n");
						}
					}
				}
			}
		}
		
		// 把域名配置更新到所有的节点
		Node[] tmpnodes = Node.listReadyNodes(session);
		String fileContent = FileUtil.getFileContent("conf/forward.service.template");
		fileContent = fileContent.replace("@@SERVICE_LIST@@", forwardBuf.toString());
		FileUtil.writeStringFile("conf/forward.service", fileContent);
		fileContent = FileUtil.getFileContent("conf/reverse.service.template");
		fileContent = fileContent.replace("@@SERVICE_LIST@@", reverseBuf.toString());
		FileUtil.writeStringFile("conf/reverse.service", fileContent);
		for(int i = 0; i < tmpnodes.length; ++i)
		{
			String cmd = "scp conf/forward.service root@"+tmpnodes[i].getIp()+":/tmp/";
			String response = SystemCmd.execCMD(cmd, 10);
			Log.info(session, tmpnodes[i], cmd+":"+response);
			cmd = "ssh root@"+tmpnodes[i].getIp()+" docker cp /tmp/forward.service dmdnsContainer:/data/bind/etc/";
			response = SystemCmd.execCMD(cmd, 10);
			Log.info(session, tmpnodes[i], cmd+":"+response);
			
			cmd = "scp conf/reverse.service root@"+tmpnodes[i].getIp()+":/tmp/";
			response = SystemCmd.execCMD(cmd, 10);
			Log.info(session, tmpnodes[i], cmd+":"+response);
			cmd = "ssh root@"+tmpnodes[i].getIp()+" docker cp /tmp/reverse.service dmdnsContainer:/data/bind/etc/";
			response = SystemCmd.execCMD(cmd, 10);
			Log.info(session, tmpnodes[i], cmd+":"+response);
			
			// 重启dns
			cmd = "ssh root@"+tmpnodes[i].getIp()+" docker exec -i dmdnsContainer rndc reconfig";
			response = SystemCmd.execCMD(cmd, 10);
			Log.info(session, tmpnodes[i], cmd+":"+response);
			
			cmd = "ssh root@"+tmpnodes[i].getIp()+" docker exec -i dmdnsContainer rndc reload";
			response = SystemCmd.execCMD(cmd, 10);
			Log.info(session, tmpnodes[i], cmd+":"+response);
		}
		
		
		
		// 更新nginx代理映射
		for(int i = 0; i < services.length; ++i)
		{
			String confDir = "http";
			if(services[i].getProto() == Service.PROTO_TCP)
			{
				template = FileUtil.getFileContent("conf/nginx_tcp.template");
				confDir = "tcp";
			}
			else
			{
				template = FileUtil.getFileContent("conf/nginx_http.template");
				confDir = "http";
			}
			
			String content = template.replace("@@SERVICE_NAME@@", services[i].getName());
			
			// 获取container容器节点IP和docker容器的主机端口
			sb.setLength(0);
			long[] containersId = services[i].getContainers().getIds();
			for(int j = 0; j < containersId.length; ++j)
			{
				Container container = (Container)session.getById(Container.class, containersId[j]);
				// 只有master节点和工作节点需要做负载均衡
				if(container.getRole() == Container.ROLE_MASTER || container.getRole() == Container.ROLE_WORKER)
				{
					Node node = container.getNode(session);
					sb.append("server "+node.getIp()+":"+container.getDockerPort()+";\n");
				}
			}
			content = content.replace("@@PROXY_RECORD_LIST@@", sb.toString());
			content = content.replace("@@PORT@@", String.format("%d", services[i].getPort()));
			//contentBuffer.append(content);
			
			// 对mysql主从分离的特殊处理
			if(services[i].getCatalog() == Service.CATELOG_MYSQL)
			{
				
				StringBuffer tmpsb = new StringBuffer();
				int slavecount = 0;
				for(int j = 0; j < containersId.length; ++j)
				{
					Container container = (Container)session.getById(Container.class, containersId[j]);
					// 只有master节点和工作节点需要做负载均衡
					if(container.getRole() == Container.ROLE_SLAVE)
					{
						Node node = container.getNode(session);
						tmpsb.append("server "+node.getIp()+":"+container.getDockerPort()+";\n");
						++slavecount;
					}
				}

				
				if(slavecount > 0)
				{
					String tmpcontent = template.replace("@@SERVICE_NAME@@", services[i].getName()+"r");
					tmpcontent = tmpcontent.replace("@@PROXY_RECORD_LIST@@", tmpsb.toString());
					tmpcontent = tmpcontent.replace("@@PORT@@", String.format("%d", services[i].getSecondPort()));
					content = content+"\n"+tmpcontent;
				}
			}
			
			// 遍历所有节点
			Node[] nodes = Node.listReadyNodes(session);
			String srcConfFileName = "conf/" + services[i].getName() + ".conf";
			String dstHostConfFileName = "/tmp/" + services[i].getName() + ".conf";
			
			String dstConfFileName = "/etc/nginx/conf.d/" + confDir + "/" + services[i].getName() + ".conf";
			for(int j = 0; j < nodes.length; ++j)
			{
				if(nodes[j].isNginxRunning())
				{
					
					content = content.replace("@@HOST@@", nodes[j].getIp());
					FileUtil.writeStringFile(srcConfFileName, content);
					String cmd = "scp " + srcConfFileName + " root@" + nodes[j].getIp() + ":" + dstHostConfFileName;
					SystemCmd.execCMD(cmd, 5);
					cmd = "ssh root@"+nodes[j].getIp()+" 'docker cp " + dstHostConfFileName + " dmnginxContainer:" + dstConfFileName + "'";
					SystemCmd.execCMD(cmd, 5);
					try
					{
						cmd = "ssh root@"+nodes[j].getIp()+" 'docker exec -i dmnginxContainer /usr/sbin/nginx -s reload'";
						SystemCmd.execCMD(cmd, 30);
					}
					catch(CommandExecuteException e)
					{
						
					}
				}
			}
		}

	}
	
	public static String getServicePortString()
	{
		if(servicePortStr == null)
		{
			try
			{
				String servicePortStartStr = XMLUtil.getString(Constant.SERVER_CONF_FILE, "/Server/service-port/start");
				String servicePortEndStr = XMLUtil.getString(Constant.SERVER_CONF_FILE, "/Server/service-port/end");
				servicePortStart = Integer.parseInt(servicePortStartStr);
				servicePortEnd = Integer.parseInt(servicePortEndStr);
				
				if(servicePortEnd < servicePortStart)
					servicePortEnd = servicePortStart + 128;
				
				int count = servicePortEnd - servicePortStart;
				String[] strings = new String[count];
				for(int i = servicePortStart; i < servicePortEnd; ++i)
				{
					strings[i - servicePortStart] = "-p "+i+":"+i;
				}
				servicePortStr = String.join(" ", strings);
			}
			catch(Exception e)
			{
				e.printStackTrace();
				System.exit(1);
			}
		}
		return servicePortStr;
	}
	
	public static String toDictString(Method m)
	{
		if(m.getName().equals("getDeployment"))
		{
			return "获取对应的部署对象";
		}
		else if(m.getName().equals("getRunningContainers"))
		{
			return "获取运行中的容器列表";
		}
		else if(m.getName().equals("listServices"))
		{
			return "列出所有的服务";
		}
		else if(m.getName().equals("agentUpdate"))
		{
			return "更新容器负载";
		}
		else if(m.getName().equals("getServicePortString"))
		{
			return "获取服务端口描述";
		}

		return LogNode.toDictString(m);
	}
	
	public static String toDictString(Field f)
	{
		if(f.getName().equals("port"))
		{
			return "服务器分配到的端口号, 在每个工作节点上都是同一个端口";
		}
		else if(f.getName().equals("secondPort"))
		{
			return "服务器分配道的第二个端口, 用做mysql的读写分离需要两个端口";
		}
		else if(f.getName().equals("name"))
		{
			return "服务名称, 和部署名称一致";
		}
		else if(f.getName().equals("containers"))
		{
			return "服务关联的容器";
		}
		else if(f.getName().equals("proto"))
		{
			return "服务的协议, HTTP:0, TCP:1, 默认为HTTP";
		}
		else if(f.getName().equals("catalog"))
		{
			return "服务的具体类型, NORMAL:0, REDIS:1, MYSQL:2, 该字段会制约proto的值";
		}
		else if(f.getName().equals("isBackup"))
		{
			return "当前服务是否在备份过程中, 备份的时候不能被打断";
		}
		else if(f.getName().equals("containers"))
		{
			return "服务关联的容器";
		}
		else if(f.getName().equals("deployment"))
		{
			return "关联的部署";
		}
		else if(f.getName().equals("backups"))
		{
			return "服务的备份列表";
		}
		
		return LogNode.toDictString(f);
	}

	
	
	public int getSecondPort() {
		return secondPort;
	}

	public void setSecondPort(int secondPort) {
		this.secondPort = secondPort;
	}

	public int getProto() {
		return proto;
	}

	public boolean isBackup() {
		return isBackup;
	}

	public void setBackup(boolean isBackup) {
		this.isBackup = isBackup;
	}

	public void setProto(int proto) {
		this.proto = proto;
	}

	public int getPort() {
		return port;
	}

	public void setPort(int port) {
		this.port = port;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Relation getContainers() {
		return containers;
	}

	public Relation getDeployment() {
		return deployment;
	}
	
	

	public Relation getBackups() {
		return backups;
	}

	public int getCatalog() {
		return catalog;
	}

	public void setCatalog(int catalog) {
		this.catalog = catalog;
	}

	@Override
	public String toString() {
		return "Service [port=" + port + ", secondPort=" + secondPort + ", name=" + name + ", proto=" + proto
				+ ", catalog=" + catalog + ", isBackup=" + isBackup + ", containers=" + containers + ", deployment="
				+ deployment + ", backups=" + backups + ", _id=" + _id + ", _v=" + _v + "]";
	}
	
	
}
